package iqq.im.event.future;

import iqq.im.QQActionListener;
import iqq.im.QQException;
import iqq.im.QQException.QQErrorCode;
import iqq.im.event.QQActionEvent;
import iqq.im.event.QQActionEvent.Type;
import iqq.im.event.QQActionFuture;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public abstract class AbstractActionFuture implements QQActionFuture, QQActionListener {
    private QQActionListener proxyListener;
    private BlockingQueue<QQActionEvent> eventQueue;
    private volatile boolean isCanceled;
    private volatile boolean hasEvent;

    public AbstractActionFuture(QQActionListener proxyListener) {
        this.hasEvent = true;
        this.proxyListener = proxyListener;
        this.eventQueue = new LinkedBlockingQueue<QQActionEvent>();
    }

    public QQActionEvent waitEvent() throws QQException {
        if (!hasEvent) {
            return null;
        }
        try {
            QQActionEvent event = eventQueue.take();
            hasEvent = !isFinalEvent(event);
            return event;
        } catch (InterruptedException e) {
            throw new QQException(QQErrorCode.WAIT_INTERUPPTED, e);
        }
    }

    public QQActionEvent waitEvent(long timeoutMs) throws QQException {
        QQActionEvent event = null;
        if (!hasEvent) {
            return null;
        }
        try {
            event = eventQueue.poll(timeoutMs, TimeUnit.MICROSECONDS);
        } catch (InterruptedException e) {
            throw new QQException(QQErrorCode.WAIT_INTERUPPTED, e);
        }
        if (event == null) {
            throw new QQException(QQErrorCode.WAIT_TIMEOUT);
        } else {
            hasEvent = !isFinalEvent(event);
            return event;
        }
    }

    public QQActionEvent waitFinalEvent() throws QQException {
        QQActionEvent event = null;
        while ((event = waitEvent()) != null) {
            if (isFinalEvent(event)) {
                return event;
            }
        }
        throw new QQException(QQErrorCode.UNKNOWN_ERROR);
    }

    public QQActionEvent waitFinalEvent(long timeoutMs) throws QQException {
        QQActionEvent event = null;
        long start = System.currentTimeMillis();
        while ((event = waitEvent(timeoutMs)) != null) {
            long end = System.currentTimeMillis();
            if (isFinalEvent(event)) {
                return event;
            } else {
                timeoutMs -= end - start;
                start = System.currentTimeMillis();
            }
        }
        throw new QQException(QQErrorCode.UNKNOWN_ERROR);
    }

    private boolean isFinalEvent(QQActionEvent event) {
        QQActionEvent.Type type = event.getType();
        return type == Type.EVT_CANCELED || type == Type.EVT_ERROR || type == Type.EVT_OK;
    }

    public void onActionEvent(QQActionEvent event) {
        if (proxyListener != null) {
            proxyListener.onActionEvent(event);
        }
        eventQueue.add(event);
    }

    public boolean isCanceled() {
        return isCanceled;
    }

    public void setCanceled(boolean isCanceled) {
        this.isCanceled = isCanceled;
    }

    public void notifyActionEvent(QQActionEvent.Type type, Object target) {
        onActionEvent(new QQActionEvent(type, target, this));
    }

    /**
     * @return the proxyListener
     */
    public QQActionListener getProxyListener() {
        return proxyListener;
    }
}